package com.rparoxysm.tribase.weather;

import com.rparoxysm.tribase.data.types.WeatherCondition;
import com.rparoxysm.tribase.data.types.WeatherConditionException;
import com.rparoxysm.tribase.data.types.WeatherConditionKey;
import com.rparoxysm.util.Debug;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class WeatherArchiver implements Runnable {
    private int MAX_HISTORY_AGE = 5;  // purge history map if not used in 5 min
    private int UPDATE_CONDITIONS_TIME = 1;
    private String HISTORY_FILE_NAME = "weather_conditions.xml";
    private static final String CLASS_NAME = "WeatherArchiver";
    private String WEATHER_CONDITIONS_URI = 
	"http://www.weather.gov/data/current_obs/KDCA.xml";

    private static Debug debug = new Debug(CLASS_NAME);

    private Calendar historyPurgeTime;
    private Calendar nextUpdateTime;

    private Map<WeatherConditionKey, WeatherCondition> historyMap;
    private Map<WeatherConditionKey, WeatherCondition> cachedConditions;
    private WeatherCondition lastCondition;

    public WeatherArchiver() {
	historyMap = null;
	lastCondition = null;
	cachedConditions = new HashMap<WeatherConditionKey, WeatherCondition>();
    }

    public synchronized void setHistoryFileName(String filename) {
	HISTORY_FILE_NAME = filename;
    }

    public synchronized void setWeatherConditionsUri(String uri) {
	WEATHER_CONDITIONS_URI = uri;
    }

    public WeatherCondition getWeather(Calendar dtg, String station) 
	throws WeatherConditionNotFoundException {
	return getWeather(dtg, station, HISTORY_FILE_NAME);
    }

    public synchronized WeatherCondition getWeather(Calendar dtg, String where,
						    String filename) 
    throws WeatherConditionNotFoundException {
	debug.print(Debug.TRACE, "getWeather(" + 
		    WeatherCondition.formatDate(dtg) + ", " + filename + ")");

	WeatherCondition cond = null;
	WeatherConditionKey key = new WeatherConditionKey(dtg, where);

	try {
	    cond = findConditions(cachedConditions, key);
	} catch (WeatherConditionNotFoundException e) {
	    debug.print(Debug.INFO, "Weather Conditions for " + key +
			" are not cached.  Reading history file.");
	    cond = searchHistoryFile(key, filename);
	    cachedConditions.put(key, cond);
	    debug.print(Debug.INFO, "Added " + key + " to in-memory cache");
	}
	return cond;
    }

    private synchronized void updateCurrentConditions(String uri) {
	debug.print(Debug.INFO, "Update current weather conditions?");

	if(nextUpdateTime.before(Calendar.getInstance())) {
	    updateNextUpdateTime();

	    WeatherConditionHandler handler = new WeatherConditionHandler();
	    
	    // Rename the old history to a temp space so we can prepend
	    // the <tribase_weather_history> tag
	    File f = new File(HISTORY_FILE_NAME);
	    f.renameTo(new File(HISTORY_FILE_NAME + "~"));
	    
	    BufferedWriter out = null;
	    try {
		// Open and prepare the destination file
		out = new BufferedWriter(new FileWriter(HISTORY_FILE_NAME));
		out.write("<tribase_weather_history>\n");
	    } catch (IOException ioe) {
		debug.print(Debug.WARN, "WARNING: " + ioe.getMessage());
	    }

	    try {
		// Open the source file
		BufferedReader in = 
		    new BufferedReader(new FileReader(HISTORY_FILE_NAME + "~"));

		// Copy everything from the old to the new history file
		String line = null;
		while( (line = in.readLine()) != null) {
		    if(!line.equals("<tribase_weather_history>") &&
		       !line.equals("</tribase_weather_history>") &&
		       !line.equals("")) {
			out.write(line + "\n");
		    }
		}
		in.close();
	    } catch (IOException ioe) {
		debug.print(Debug.WARN, "WARNING: " + ioe.getMessage());
	    }

	    try {
		List conditions = handler.parseXml(uri);
		Iterator condIter = conditions.iterator();
		while(condIter.hasNext()) {
		    WeatherCondition cond = (WeatherCondition) condIter.next();
		    WeatherConditionKey key =  
			new WeatherConditionKey(cond.getTime(), 
						cond.getStation());

		    WeatherCondition oldCond = null;

		    try {
			oldCond = getWeather(cond.getTime(), cond.getStation(),
					     HISTORY_FILE_NAME + "~");
		    } catch (WeatherConditionNotFoundException wce) {}

		    if(oldCond == null) {
			out.write(cond.toXml());
			lastCondition = cond;
			if(historyMap != null) {
			    historyMap.put(key, cond);
			}
			debug.print(Debug.INFO, 
				    "Added " + key + " to history file");
		    }
		}
		out.write("\n</tribase_weather_history>\n");
		out.close();
	    } catch (WeatherConditionException e) {
		debug.print(Debug.ERROR, e.getMessage());
	    } catch (IOException ioe) {
		debug.print(Debug.ERROR, "Error updating weather history: " +
			    ioe.getMessage());
	    }
	}
    }

    private synchronized void purgeHistory() {
	if(historyPurgeTime.before(Calendar.getInstance())) {
	    debug.print(Debug.INFO, "Purging in-memory history");
	    historyMap = null;
	}
    }

    private void updateHistoryPurgeTime() {
	historyPurgeTime = Calendar.getInstance();
	historyPurgeTime.add(Calendar.MINUTE, MAX_HISTORY_AGE);
    }

    private void updateNextUpdateTime() {
	nextUpdateTime = Calendar.getInstance();
	nextUpdateTime.add(Calendar.HOUR, UPDATE_CONDITIONS_TIME);
    }

    private void readHistoryFile(String uri) {
	debug.print(Debug.TRACE, "ENTER readHistoryFile(" + uri + ")");

	WeatherConditionHandler handler = new WeatherConditionHandler();

	try {
	    List conditions = handler.parseXml(uri);
	    if(conditions != null) {
		historyMap = 
		    new HashMap<WeatherConditionKey, WeatherCondition>();
	    
		Iterator condIter = conditions.iterator();
		while(condIter.hasNext()) {
		    WeatherCondition cond = (WeatherCondition) condIter.next();
		    WeatherConditionKey key = 
			new WeatherConditionKey(cond.getTime(), 
						cond.getStation());
		    historyMap.put(key, cond);
		    debug.print(Debug.INFO, "Loaded " + key + " from history file");
		}
		updateHistoryPurgeTime();
	    }
	} catch (WeatherConditionException e) {
	    debug.print(Debug.ERROR, e.getMessage());
	}
    }
	
    private WeatherCondition searchHistoryFile(WeatherConditionKey key, 
					       String filename) 
	throws WeatherConditionNotFoundException {
	WeatherCondition cond = null;

	if(historyMap == null) {
	    // build the history map
	    readHistoryFile(filename);
	}
	
	if(historyMap != null) {
	    cond = findConditions(historyMap, key);
	    updateHistoryPurgeTime();  // exception was thrown if not found
	}
	
	if(cond == null) {
	    throw new WeatherConditionNotFoundException("No weather history available");
	}
	return cond;
    }

    private WeatherCondition 
	findConditions(Map<WeatherConditionKey, WeatherCondition> map, 
		       WeatherConditionKey key)
	throws WeatherConditionNotFoundException {
	
	WeatherCondition cond = map.get(key);
	if(cond == null) {
	    throw new WeatherConditionNotFoundException(
                "No weather condtion data for " + key);
	}
	return cond;
    }

    public synchronized void addWeather(WeatherCondition cond) {
	// Check we already have data for the same date & time
	
	// If we don't have data in the history log, compare again
	
    }

    public void run() {
	nextUpdateTime = Calendar.getInstance();
	
	debug.print(0, "nextUpdateTime = " + 
		    WeatherCondition.formatDate(nextUpdateTime));

	try {
	    lastCondition = getWeather(nextUpdateTime, null, HISTORY_FILE_NAME + "~");
	} catch (WeatherConditionNotFoundException e) {}


	while(true) {
	    updateCurrentConditions(WEATHER_CONDITIONS_URI);

	    try {
		Thread.sleep(MAX_HISTORY_AGE * 60 * 1000);
	    } catch (Exception e) {
		debug.print(Debug.ERROR, e.getMessage());
	    }
	}
    }
    
    public static void main(String args[]) {
	Debug.setLevel(CLASS_NAME, Debug.ALL);

	WeatherArchiver updateThread = new WeatherArchiver();

	if(args.length == 1) {
	    updateThread.setHistoryFileName(args[0]);
	}

	new Thread(updateThread).start();
	try { 
	    Thread.sleep(1000); 
	    /*
	    // get the first condition
	    Calendar oneTime = new GregorianCalendar(2008, 03, 27, 00, 52, 0);
	    WeatherCondition oneCond = updateThread.getWeather(oneTime);
	    System.out.println(oneCond);
	    Thread.sleep(1000); 
	    
	    // get the second condition
	    Calendar twoTime = new GregorianCalendar(2008, 03, 27, 01, 52, 0);
	    WeatherCondition twoCond = updateThread.getWeather(twoTime);
	    System.out.println(twoCond);
	    Thread.sleep(1000); 
	    
	    // get the third condition
	    Calendar threeTime = new GregorianCalendar(2008, 03, 27, 02, 52, 0);
	    WeatherCondition threeCond = updateThread.getWeather(threeTime);
	    System.out.println(threeCond);
	    Thread.sleep(1000); 
	    
	    // get the fourth condition
	    Calendar fourTime = new GregorianCalendar(2008, 03, 27, 03, 52, 0);
	    WeatherCondition fourCond = updateThread.getWeather(fourTime);
	    System.out.println(fourCond);
	    Thread.sleep(1000); 
	    */	    
	} catch (Exception e) {
	    debug.print(Debug.ERROR, e.getMessage());
	}
	while(true) {
	    try {
		Thread.sleep(1000); 
	    } catch (Exception e) {
		debug.print(Debug.ERROR, e.getMessage());
	    }
	}
    }
}
